// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package modcmd

import (
	"context"
	"encoding/json"
	"os"
	"runtime"

	"cmd/go/internal/base"
	"cmd/go/internal/cfg"
	"cmd/go/internal/modfetch"
	"cmd/go/internal/modload"

	"golang.org/x/mod/module"
	"golang.org/x/mod/semver"
)

var cmdDownload = &base.Command{
	UsageLine: "go mod download [-x] [-json] [modules]",
	Short:     "download modules to local cache",
	Long: `
Download downloads the named modules, which can be module patterns selecting
dependencies of the main module or module queries of the form path@version.

With no arguments, download applies to the modules needed to build and test
the packages in the main module: the modules explicitly required by the main
module if it is at 'go 1.17' or higher, or all transitively-required modules
if at 'go 1.16' or lower.

The go command will automatically download modules as needed during ordinary
execution. The "go mod download" command is useful mainly for pre-filling
the local cache or to compute the answers for a Go module proxy.

By default, download writes nothing to standard output. It may print progress
messages and errors to standard error.

The -json flag causes download to print a sequence of JSON objects
to standard output, describing each downloaded module (or failure),
corresponding to this Go struct:

    type Module struct {
        Path     string // 模块路径
        Version  string // 模块版本
        Error    string // 加载模块时出错
        Info     string // 缓存的绝对路径。信息文件
        GoMod    string // 缓存的绝对路径。mod file 
        Zip      string // 缓存的绝对路径。zip文件
        Dir      string // 缓存的源根目录的绝对路径
        Sum      string // 路径的校验和，版本（如go.sum）
        GoModSum string // go的校验和。mod（如go.sum）
    }

The -x flag causes download to print the commands download executes.

See https:// golang。org/ref/mod#go mod download了解更多关于go mod download的信息。

See https:// golang。org/ref/mod#version querys了解有关版本查询的更多信息。
	`,
}

var downloadJSON = cmdDownload.Flag.Bool("json", false, "")

func init() {
	cmdDownload.Run = runDownload // 中断初始循环

	// TODO（jayconrod）：https:
	cmdDownload.Flag.BoolVar(&cfg.BuildX, "x", false, "")
	base.AddModCommonFlags(&cmdDownload.Flag)
}

type moduleJSON struct {
	Path     string `json:",omitempty"`
	Version  string `json:",omitempty"`
	Error    string `json:",omitempty"`
	Info     string `json:",omitempty"`
	GoMod    string `json:",omitempty"`
	Zip      string `json:",omitempty"`
	Dir      string `json:",omitempty"`
	Sum      string `json:",omitempty"`
	GoModSum string `json:",omitempty"`
}

func runDownload(ctx context.Context, cmd *base.Command, args []string) {
	modload.InitWorkfile()

	// 检查模块是否已启用，以及我们是否在模块中。
	modload.ForceUseModules = true
	modload.ExplicitWriteGoMod = true
	haveExplicitArgs := len(args) > 0

	if modload.HasModRoot() || modload.WorkFilePath() != "" {
		modload.LoadModFile(ctx) // 填写主要模块

		if haveExplicitArgs {
			for _, mainModule := range modload.MainModules.Versions() {
				targetAtUpgrade := mainModule.Path + "@upgrade"
				targetAtPatch := mainModule.Path + "@patch"
				for _, arg := range args {
					switch arg {
					case mainModule.Path, targetAtUpgrade, targetAtPatch:
						os.Stderr.WriteString("go: skipping download of " + arg + " that resolves to the main module\n")
					}
				}
			}
		} else if modload.WorkFilePath() != "" {
			// TODO（#44435）：考虑一下下载
			// 正确模块集的正确查询是什么。另请参见
			// https:
			args = []string{"all"}
		} else {
			mainModule := modload.MainModules.Versions()[0]
			modFile := modload.MainModules.ModFile(mainModule)
			if modFile.Go == nil || semver.Compare("v"+modFile.Go.Version, modload.ExplicitIndirectVersionV) < 0 {
				if len(modFile.Require) > 0 {
					args = []string{"all"}
				}
			} else {
				// /从Go 1.17开始，即Go。mod file明确要求每个模块
				// 提供主模块导入的任何包。
				// “go mod download”通常在测试
				// 主模块中的软件包之前运行，因此默认情况下，我们不应下载其他
				// （假定与主模块中的软件包无关）。
				// 请参阅https:
				// 
				// 但是，我们还需要加载完整的模块图，以确保
				// 我们下载了足够的模块图，可以运行“go list all”、“
				// ”go mod graph”和类似命令。
				_ = modload.LoadModGraph(ctx, "")

				for _, m := range modFile.Require {
					args = append(args, m.Mod.Path)
				}
			}
		}
	}

	if len(args) == 0 {
		if modload.HasModRoot() {
			os.Stderr.WriteString("go: no module dependencies to download\n")
		} else {
			base.Errorf("go: no modules specified (see 'go help mod download')")
		}
		base.Exit()
	}

	downloadModule := func(m *moduleJSON) {
		var err error
		m.Info, err = modfetch.InfoFile(m.Path, m.Version)
		if err != nil {
			m.Error = err.Error()
			return
		}
		m.GoMod, err = modfetch.GoModFile(m.Path, m.Version)
		if err != nil {
			m.Error = err.Error()
			return
		}
		m.GoModSum, err = modfetch.GoModSum(m.Path, m.Version)
		if err != nil {
			m.Error = err.Error()
			return
		}
		mod := module.Version{Path: m.Path, Version: m.Version}
		m.Zip, err = modfetch.DownloadZip(ctx, mod)
		if err != nil {
			m.Error = err.Error()
			return
		}
		m.Sum = modfetch.Sum(mod)
		m.Dir, err = modfetch.Download(ctx, mod)
		if err != nil {
			m.Error = err.Error()
			return
		}
	}

	var mods []*moduleJSON
	type token struct{}
	sem := make(chan token, runtime.GOMAXPROCS(0))
	infos, infosErr := modload.ListModules(ctx, args, 0)
	if !haveExplicitArgs {
		// “go mod download”有时在没有参数的情况下运行，以预填充
		// 模块缓存。它可能会获取不需要在主模块中构建包
		// 的模块。这通常不是故意的，所以不要为
		// 下载的模块（golang.org/issue/45332）节省费用。我们仍在修复go中的
		// 不一致。尽管如此。
		// 
		// TODO（#45551）：将来，如果go，请报告错误。mod或go。sum需要
		// 在加载构建列表后更新。这可能需要将
		// 模式设置为“mod”或“readonly”，具体取决于haveExplicitTargets。
		if err := modload.WriteGoMod(ctx); err != nil {
			base.Fatalf("go: %v", err)
		}
	}

	for _, info := range infos {
		if info.Replace != nil {
			info = info.Replace
		}
		if info.Version == "" && info.Error == nil {
			// 主模块或被文件路径替换的模块。
			// 无需下载。
			continue
		}
		m := &moduleJSON{
			Path:    info.Path,
			Version: info.Version,
		}
		mods = append(mods, m)
		if info.Error != nil {
			m.Error = info.Error.Err
			continue
		}
		sem <- token{}
		go func() {
			downloadModule(m)
			<-sem
		}()
	}

	// 填充信号通道以等待goroutines完成。
	for n := cap(sem); n > 0; n-- {
		sem <- token{}
	}

	if *downloadJSON {
		for _, m := range mods {
			b, err := json.MarshalIndent(m, "", "\t")
			if err != nil {
				base.Fatalf("go: %v", err)
			}
			os.Stdout.Write(append(b, '\n'))
			if m.Error != "" {
				base.SetExitStatus(1)
			}
		}
	} else {
		for _, m := range mods {
			if m.Error != "" {
				base.Errorf("go: %v", m.Error)
			}
		}
		base.ExitIfErrors()
	}

	// 如果有显式参数，请更新go。mod，尤其是go。总和
	// /'go mod下载mod@version'是一种不使用
	// /'进行加法的有用方法mod@version，这可能还有其他副作用。我们在
	// 中打印了一些错误消息提示。
	// 
	// 不要为没有参数的“go mod download”保存金额；见上面的评论。
	if haveExplicitArgs {
		if err := modload.WriteGoMod(ctx); err != nil {
			base.Errorf("go: %v", err)
		}
	}

	// 如果与某些请求的包匹配时出现错误，请立即发出该错误
	// （在我们为已成功下载的模块编写校验和
	// 之后）。
	if infosErr != nil {
		base.Errorf("go: %v", infosErr)
	}
}
